Skip to content

feat: Support version history and rollback for traffic rules#1477

Open
mochengqian wants to merge 47 commits into
apache:developfrom
mochengqian:feat/Support-version-history-and-rollback-for-traffic-rules
Open

feat: Support version history and rollback for traffic rules#1477
mochengqian wants to merge 47 commits into
apache:developfrom
mochengqian:feat/Support-version-history-and-rollback-for-traffic-rules

Conversation

@mochengqian

@mochengqian mochengqian commented May 22, 2026

Copy link
Copy Markdown
Contributor

Closes #1473.

Summary

This PR adds traffic rule version history, diff, and rollback support for governor-managed traffic rules in Dubbo Admin.

Covered rule kinds:

  • Condition Route
  • Tag Route
  • Dynamic Config / Configurator

Live traffic rules remain owned by the existing ResourceManager, registry, and dynamic configuration write paths. RuleVersion is an audit/history ledger and rollback source; it is not the live source of truth.

Versioning for supported governance rule mutations is fail-closed. Create, update, delete, and rollback must record the version ledger entry before the registry mutation is allowed to proceed. maxVersionsPerRule=0 disables retention trimming only; it does not disable version recording.

What changed

Backend

  • Adds RuleVersion as a first-class resource for recording traffic rule history entries.
  • Adds backend APIs for listing history, reading version details, comparing versions, comparing a version against current live state, and rolling back to a previous CREATE/UPDATE version.
  • Records version ledger entries for create, update, delete, and rollback before the corresponding ResourceManager write.
  • Restores the original per-rule distributed lock around the full mutation critical section:
    • load current live rule / ensure baseline
    • append RuleVersion
    • apply retention if needed
    • perform ResourceManager add/update/delete/upsert
    • return the service result
  • Captures a BOOTSTRAP baseline entry before the first admin-side modification of an existing rule with no recorded history.
  • Keeps ByParentRule indexing so history can be queried by the parent traffic rule.
  • Uses the project’s existing Console API response contract for rule-version handlers: HTTP 200 with CommonResp.code/message/data.

Frontend

  • Adds history entry points to the traffic rule pages.
  • Adds a history drawer, version detail view, diff editor, rollback dialog, rollback reason input, and refresh-after-rollback flow.
  • Keeps stale-response guards for request/selection changes.
  • Uses i18n for rule-history labels instead of hard-coded English in traffic views.
  • Does not disable rollback merely because a history entry is isLatestRecorded. The backend remains authoritative for detecting already-current / same-content rollback attempts. The UI only disables DELETE markers.

Design decisions

Versioning is fail-closed

Supported governance rule mutations do not fall back to the old raw registry write path when rule versioning is unavailable. If the versioning service, store, or append path is unavailable, the mutation returns an error and does not write the registry.

The write path is ledger-first inside the same per-rule distributed lock: record the corresponding RuleVersion entry, then perform the normal ResourceManager mutation. If ledger recording fails, registry mutation is blocked. If the later registry write fails, the API returns that error and does not report success.

Rollback authority

Rollback to a DELETE marker is rejected.

Rollback to a version whose content hash already matches the current live rule is rejected by the backend. The frontend intentionally does not try to precompute this with an extra current-hash request, because that would enlarge the API surface and still race with concurrent writes.

Diff ownership

The core versioning service owns history-to-history diff semantics:

  • explicit version ID vs explicit version ID
  • version vs previous recorded version

The console service owns against=current, because current means the live ResourceManager / registry state, not the latest recorded ledger entry.

Previous-version lookup explicitly sorts versions by VersionNo descending before selecting the previous version; callers do not depend on store return order.

DELETE versions

DELETE versions are absence markers represented by the canonical empty spec JSON marker. They are kept for audit/history continuity and to show that a rule was removed.

DELETE markers are not rollback targets. To restore deleted content, users must roll back to an earlier CREATE or UPDATE version.

BOOTSTRAP baseline

BOOTSTRAP means a baseline/import entry for an existing rule when it is first brought under version tracking. It is not limited to startup and is not registry reconciliation.

Compatibility fixes included

This PR keeps the traffic form field preservation fix because rollback and rule-history smoke paths depend on rule form round-tripping without losing fields.

The compatibility fix is intentionally scoped to preserving existing traffic rule fields such as priority, force, configVersion, and dynamic config rows while editing forms. It is not part of the core versioning model.

CI-only fixes included

The workflow cache update for the Vue build is retained as a validation-only fix so PR checks can install frontend dependencies consistently.

This has no runtime behavior change.

Removed / out of scope

Store-level CAS/OCC is not part of this PR. The previously experimental UpdateIfUnchanged and transient store error code were removed because they were not exposed through ResourceStore and were not connected to the traffic rule write path.

This PR does not add external registry reconciliation, durable intent repair APIs, or a separate conflict-resolution UI.

Tests

Backend tests cover:

  • create/update/delete normal paths writing the version ledger
  • condition/tag/dynamic create/update/delete taking the corresponding per-rule distributed lock
  • history append and registry mutation occurring inside the same lock critical section
  • rollback taking the same per-rule lock and writing history plus registry inside the lock
  • fail-closed behavior when versioning is unavailable
  • fail-closed behavior when ledger append fails before registry mutation
  • registry write failure returning an error instead of pseudo-success
  • baseline/import entry before first mutation of an existing rule
  • DELETE marker storage as an absence marker
  • DELETE marker rollback rejection
  • rollback recording a new source=ROLLBACK version
  • rollback ledger append failure blocking registry mutation
  • current diff reading live ResourceManager state
  • previous diff sorting unordered store results by VersionNo
  • explicit version ID diff
  • zero retention config still recording versions
  • rule-version handler errors using HTTP 200 + CommonResp

Frontend tests cover:

  • latest recorded non-DELETE history entries keeping rollback available
  • DELETE marker rollback being disabled
  • backend same-content rollback rejection being shown to the user
  • history list/detail stale-response guards
  • rollback dialog stale-response guard
  • dynamic config form configVersion and config row round-trip
  • condition route form edit round-trip preserving priority, force, and configVersion

Validated locally:

git diff --check apache/develop
make test
go test ./pkg/core/versioning ./pkg/console/service ./pkg/store/memory ./pkg/store/dbcommon ./pkg/core/store/...
go vet ./pkg/core/versioning ./pkg/console/service ./pkg/store/memory ./pkg/store/dbcommon ./pkg/core/store/...

cd ui-vue3
npm run test -- RuleHistoryPanel.spec.ts
npm run test -- ConfigModel.spec.ts updateByFormView.spec.ts
npm run type-check

npm run type-check still fails on existing broader project TypeScript issues outside the touched versioning/form files, including the missing declaration file for js-cookie, legacy TAB_STATE unknown typing in untouched add/update/YAML traffic views, and unrelated home/resources page type errors. All PR-touched traffic/versioning files are clear of type-check errors; the remaining failures are outside the PR diff.

Running the §9.4 smoke drill end-to-end uncovered three real defects
that the unit suite did not catch:

- RuleVersionSubscriber recorded a duplicate UPSTREAM row whenever the
  registry echoed back a no-op change identical to the latest ledger
  row (typically right after BOOTSTRAP). Now dedupes upstream events
  whose content hash already matches the current head, with an explicit
  test in versioning_test.go.
- writeVersioningResp mapped every bizerror to HTTP 200/UnknownError;
  bizerror.InvalidArgument (eg. empty rollback reason) now returns
  HTTP 400 with its original code so the frontend can act on it.
  Covered by a new handler/rule_version_test.go.
- The 409 VERSION_CONFLICT toast auto-dismissed after the default
  duration; users could miss the Reload button entirely. Pinned with
  duration: 0 so the notification stays until acknowledged.
The §9.4 smoke drill expectation "after rollback, the rule should look
the same on UI refresh" exposed a pre-existing edit-form regression:
rollback was correct at the ledger and ZK levels, but the edit form
silently dropped `priority`, `force`, and (for condition routes)
`configVersion` because they were neither rendered in the GET response
nor re-sent on save.

This is not caused by versioning, but a true round-trip is the first
flow that forces every field through the loop. Adds the missing fields
to ConditionRuleResp / TagRuleResp on the backend, and reads/writes
them in updateByFormView.vue on the frontend so a "save → rollback →
reload" cycle is now lossless.
Add /task_plan.md /findings.md /progress.md to .gitignore so the
planning-with-files workflow does not leak per-developer working
memory into the repo.
@mochengqian mochengqian force-pushed the feat/Support-version-history-and-rollback-for-traffic-rules branch from 6deb25e to 9596f7e Compare May 22, 2026 05:19
@mochengqian mochengqian changed the title Support version history and rollback for traffic rules feat: Support version history and rollback for traffic rules May 22, 2026
@mochengqian mochengqian marked this pull request as draft May 23, 2026 03:22
@mochengqian mochengqian force-pushed the feat/Support-version-history-and-rollback-for-traffic-rules branch from 2471544 to d2a6ddc Compare May 23, 2026 05:58
@mochengqian mochengqian marked this pull request as ready for review May 23, 2026 12:24
@mochengqian mochengqian force-pushed the feat/Support-version-history-and-rollback-for-traffic-rules branch from 990402d to b525c2b Compare May 24, 2026 01:02
@robocanic robocanic requested a review from Copilot May 24, 2026 10:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces immutable version history, diff viewing, and rollback for governor-managed traffic rules (Condition Route, Tag Route, Dynamic Config) in Dubbo Admin, along with optimistic concurrency control to prevent silent overwrites.

Changes:

  • Backend: adds a versioning subsystem (stores, service, subscriber, bootstrap, intent workflow) plus REST endpoints for listing/getting/diffing/rollback and intent repair/abandon.
  • Frontend: adds shared history/diff/rollback UI components, wires them into rule pages, and threads expectedVersionId through mutations with 409 conflict handling.
  • Store/core plumbing: adds ListResources() and aligns empty-index ListByIndexes semantics across memory/GORM stores.

Reviewed changes

Copilot reviewed 62 out of 62 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
ui-vue3/src/views/traffic/tagRule/tabs/updateByYAMLView.vue Adds expectedVersionId concurrency + version-error notifications for YAML editing.
ui-vue3/src/views/traffic/tagRule/tabs/updateByFormView.vue Threads version precondition into form updates and handles version conflicts.
ui-vue3/src/views/traffic/tagRule/tabs/formView.vue Integrates history panel entry point and current version badge.
ui-vue3/src/views/traffic/tagRule/index.vue Uses current version id precondition on delete and conflict notifications.
ui-vue3/src/views/traffic/routingRule/tabs/updateByYAMLView.vue Adds expectedVersionId concurrency + version-error notifications for YAML editing.
ui-vue3/src/views/traffic/routingRule/tabs/updateByFormView.vue Threads version precondition into form updates and handles version conflicts.
ui-vue3/src/views/traffic/routingRule/tabs/formView.vue Integrates history panel entry point and current version badge.
ui-vue3/src/views/traffic/routingRule/index.vue Uses current version id precondition on delete and conflict notifications.
ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue Adds history panel + expectedVersionId concurrency for YAML-based configurator edits.
ui-vue3/src/views/traffic/dynamicConfig/tabs/formView.vue Adds history panel + expectedVersionId concurrency for form-based configurator edits.
ui-vue3/src/views/traffic/dynamicConfig/index.vue Uses current version id precondition on delete and conflict notifications.
ui-vue3/src/views/traffic/_shared/ruleVersion.ts Shared helpers for fetching current version state and displaying conflict/pending notifications.
ui-vue3/src/views/traffic/_shared/RuleHistoryPanel.vue New history panel orchestrating list/view/diff/rollback flows.
ui-vue3/src/views/traffic/_shared/RuleHistoryDrawer.vue Drawer UI for version timeline and actions.
ui-vue3/src/views/traffic/_shared/RuleDiffEditor.vue Monaco diff editor wrapper for version comparisons.
ui-vue3/src/mocks/handlers/tagRule.ts MSW: simulates version conflicts/pending and records admin writes for tag rules.
ui-vue3/src/mocks/handlers/routingRule.ts MSW: simulates version conflicts/pending and records admin writes for condition rules.
ui-vue3/src/mocks/handlers/dynamicConfig.ts MSW: simulates version conflicts/pending and records admin writes for configurators (with URL decoding).
ui-vue3/src/mocks/handlers/ruleVersion.ts MSW: full in-browser mock ledger + diff/rollback + intent repair/abandon flows.
ui-vue3/src/mocks/handlers.ts Registers ruleVersion MSW handlers.
ui-vue3/src/base/http/request.ts Suppresses generic error toasts for version conflict/pending so the dedicated notifications can be used.
ui-vue3/src/api/service/traffic.ts Adds versioning API surface + threads expectedVersionId into existing mutations.
pkg/store/memory/store.go Adds ListResources() with sorting and error propagation.
pkg/store/memory/store_test.go Tests ListResources() sorting and empty-index semantics.
pkg/store/dbcommon/gorm_store.go Adds ListResources() and aligns empty-index semantics with memory store.
pkg/store/dbcommon/gorm_store_test.go Tests empty-index behavior and ListResources() ordering.
pkg/core/versioning/types.go Defines version/meta/intent models, enums, and shared errors.
pkg/core/versioning/subscriber.go Records upstream changes and attaches events to matching admin intents.
pkg/core/versioning/store.go In-memory immutable ledger store + intent lifecycle + retention trimming + dedup.
pkg/core/versioning/store_gorm.go GORM-backed immutable ledger store + intent lifecycle + trimming + dedup.
pkg/core/versioning/store_gorm_test.go Tests GORM store migration, trimming, dedup, intents, and concurrency monotonicity.
pkg/core/versioning/service.go Versioning service API: list/get/diff, expected-version check, intents, repair helpers.
pkg/core/versioning/normalize.go Canonical JSON normalization and sha256 hashing for dedup and intent matching.
pkg/core/versioning/e2e_rollback_drill_test.go End-to-end drill covering bootstrap, admin edit, upstream push, rollback, and retention trim.
pkg/core/versioning/component.go Runtime component wiring: store selection, event subscriptions, startup repair/bootstrap scan.
pkg/core/store/store.go Extends ResourceStore interface with ListResources().
pkg/core/manager/manager.go Adds List(rk) to manager via store’s ListResources().
pkg/core/manager/manager_test.go Verifies manager List returns sorted resources.
pkg/core/events/eventbus.go Adds SourceRegistryContextKey and clarifies event context immutability expectations.
pkg/core/discovery/subscriber/zk_config.go Adds ZK delete nil-guard and emits source-registry context for version attribution.
pkg/core/discovery/subscriber/zk_config_test.go Tests delete path uses local old rule and missing-local-rule is a noop.
pkg/core/bootstrap/bootstrap.go Registers the versioning component as an optional bootstrap component.
pkg/console/service/tag_rule.go Wraps tag rule mutations with intent-based versioning and expectedVersionId checks.
pkg/console/service/configurator_rule.go Wraps configurator mutations with intent-based versioning and expectedVersionId checks.
pkg/console/service/condition_rule.go Wraps condition rule mutations with intent-based versioning and expectedVersionId checks.
pkg/console/service/rule_version.go Adds console-layer services for version list/get/diff/rollback and intent repair/abandon.
pkg/console/service/rule_version_test.go Covers conflict handling, pending intents, rollback paths, delete marker semantics, and intent recovery.
pkg/console/model/tag_rule.go Exposes force and priority fields in tag rule responses.
pkg/console/model/condition_rule.go Exposes force and priority fields in condition rule responses.
pkg/console/handler/tag_rule.go Adds mutation options parsing and maps versioning conflicts/pending to 409.
pkg/console/handler/configurator_rule.go Adds mutation options parsing and maps versioning conflicts/pending to 409.
pkg/console/handler/condition_rule.go Adds mutation options parsing and maps versioning conflicts/pending to 409.
pkg/console/handler/rule_version.go Implements versioning REST endpoints and error-to-HTTP mapping.
pkg/console/handler/rule_version_test.go Tests status/code mapping for InvalidArgument and pending intent id propagation.
pkg/console/router/router.go Registers versioning endpoints under the existing traffic rule routes + intent ops routes.
pkg/console/context/context.go Exposes RuleVersioning service from the runtime component.
pkg/console/component_test.go Ensures auth middleware blocks rollback endpoint without a session.
pkg/config/versioning/config.go Adds versioning config block, defaults, sanitize and validation.
pkg/config/versioning/config_test.go Tests defaults, sanitize, and validation for versioning config.
pkg/config/app/admin.go Adds versioning config to AdminConfig and ensures defaults for nil config blocks.
pkg/config/app/admin_test.go Tests AdminConfig defaulting behavior when Versioning config is missing.
Comments suppressed due to low confidence (1)

ui-vue3/src/views/traffic/dynamicConfig/tabs/YAMLView.vue:205

  • The catch block only calls notifyRuleVersionError(...) and then swallows the exception. This can hide non-versioning failures (notably YAML parse errors from yaml.load or other runtime exceptions) because the request interceptor won’t run for those cases. Consider rethrowing or showing a generic error toast when notifyRuleVersionError returns false.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/core/versioning/component.go Outdated
Comment thread ui-vue3/src/views/traffic/tagRule/tabs/updateByYAMLView.vue
Comment thread ui-vue3/src/views/traffic/routingRule/tabs/updateByYAMLView.vue
Comment thread pkg/console/handler/rule_version.go Outdated
Comment thread pkg/console/handler/rule_version.go Outdated
…history-and-rollback-for-traffic-rules

Revert "feat: support traffic rule version history and rollback"

@robocanic robocanic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢PR,有几个关键问题,期待后续和我讨论更好的方案:

  1. 架构设计问题,为什么不把ruleVersion视为一种Resource?这样就能复用现有的Resource的链路,能减少很多重复代码且代码链路更清晰。
  2. 没有任何注释,很多代码只能给AI才能读懂。
  3. golang不提倡过度封装,很多宽泛的接口层没有意义。

Comment thread pkg/config/app/admin.go Outdated
Comment thread pkg/config/app/admin.go Outdated
Comment thread pkg/config/app/admin.go Outdated
Comment thread pkg/core/store/store.go Outdated
Comment thread pkg/core/versioning/service.go Outdated
Comment thread pkg/core/versioning/store.go Outdated
@mochengqian mochengqian marked this pull request as draft June 14, 2026 03:41
mochengqian added a commit to mochengqian/dubbo-admin that referenced this pull request Jun 14, 2026
Remove unused ResourceManager.List() method and simplify versioning API
by converting Service from interface to concrete type.

Changes:
- Remove ResourceManager.List() from manager interface and all implementations
- Remove versioning.Service interface, rename service struct to Service (exported)
- Add GetStore() to ResourceManager for bootstrap access to rule stores
- Add public methods to versioning.Service: GetIntent, MarkIntentFailedWithReason,
  CurrentMeta, GetVersion (replaces Store() accessor)
- Update all callers to use *versioning.Service instead of interface
- Update component.Service() to return *Service
- Fix all tests to implement GetStore (returns nil for test fakes)

All tests pass:
- pkg/core/versioning: 23/23 pass
- pkg/console/service (RuleVersion): 11/11 pass

Related: apache#1477 Phase 1.2 (manager.List removal, Service interface cleanup)
mochengqian added a commit to mochengqian/dubbo-admin that referenced this pull request Jun 14, 2026
Define RuleVersion proto message and Go resource wrapper to store
version history as first-class resources in the resource store.

Changes:
- Add api/mesh/v1alpha1/rule_version.proto with RuleVersion message
  * parent_rule_kind, parent_rule_mesh, parent_rule_name: parent rule identity
  * version_no, content_hash: version metadata
  * spec_json: JSON snapshot of rule spec at this version
  * operation, source, author, reason: mutation context
  * rolled_back_from_id: set if this is a rollback
  * created_at, committed_at: timestamps
- Generate rule_version.pb.go with protoc
- Add pkg/core/resource/apis/mesh/v1alpha1/rule_version_types.go
  * RuleVersionResource and RuleVersionResourceList types
  * Implement Resource interface (ResourceKind, ResourceKey, etc.)
  * Register with coremodel.RegisterResourceSchema

This enables storing version history in the resource store alongside
traffic rules, preparing for migration from versioning.Store tables.

Related: apache#1477 Phase 2.1
mochengqian added a commit to mochengqian/dubbo-admin that referenced this pull request Jun 14, 2026
Add index to efficiently query RuleVersion resources by parent rule.

Changes:
- Create pkg/core/store/index/rule_version.go
- Define ByParentRuleIndexName constant
- Register byParentRule indexer for RuleVersionKind
- Index key format: "<parent_kind>/<parent_mesh>/<parent_name>"
  Example: "ConditionRoute/default/my-rule"

This enables fast lookup of all version history for a given rule
without scanning the entire RuleVersion store.

Related: apache#1477 Phase 2.2
@mochengqian mochengqian marked this pull request as draft June 20, 2026 09:12
Comment thread .github/workflows/ci.yml
Comment on lines -72 to +74
- name: Cache dependencies
# ref: https://github.com/actions/cache/blob/main/examples.md#go---module
uses: actions/cache@v4
with:
# Cache, works only on Linux
path: |
~/.cache/go-build
~/go/pkg/mod
# Cache key
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
# An ordered list of keys to use for restoring the cache if no cache hit occurred for key
restore-keys: |
${{ runner.os }}-go-
cache-dependency-path: go.sum
- name: Check Code Format
run: make fmt && git status && [[ -z `git status -s` ]]
run: make fmt && git status && [[ -z $(git status -s) ]]

@mochengqian mochengqian Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里修改的原因是,PR CI 在可靠运行 Go 测试之前就已失败(CI failure 有明确日志证据,在failed check 82487011873)。

失败的 GitHub Actions 任务显示了手动 Go 模块缓存恢复时出现的 tar 恢复错误,包括:

Failed to restore: "/usr/bin/tar" failed
Cannot open: File exists

该冲突源于缓存了整个 ~/go/pkg/mod 目录,而 Go 1.24/工具链管理机制以及 actions/setup-go 本身已会在模块缓存下填充由工具链管理的文件。在已存在工具链文件的情况下恢复该缓存,导致 Go 1.24 矩阵任务失败。

本次修复移除了针对 Go 模块的冗余手动 actions/cache 步骤,改为由 actions/setup-go@v5go.sum 作为依赖键来处理 Go 缓存。构建或测试命令本身未作更改:工作流仍然执行相同的前端构建、make fmt checkmake test

变更后,两个 Go CI 矩阵任务均通过:

  • CI(ubuntu-latest - Go 1.24):成功
  • CI(ubuntu-latest - Go 1.23):成功

@mochengqian mochengqian marked this pull request as ready for review June 20, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 86 out of 88 changed files in this pull request and generated 2 comments.

Files not reviewed (2)
  • api/mesh/v1alpha1/rule_intent.pb.go: Generated file
  • api/mesh/v1alpha1/rule_version.pb.go: Generated file

Comment thread pkg/core/versioning/normalize.go Outdated
Comment thread ui-vue3/src/views/traffic/tagRule/tabs/formView.vue Outdated
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@mochengqian

Copy link
Copy Markdown
Contributor Author

It is ready for review. @robocanic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Support version history and rollback for traffic rules

3 participants